/**************************************************************

Copyright Astrium Services GEO-Information / Spot Image
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

 ***************************************************************/
package com.spotimage.catalog.client.ui;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.google.gwt.core.client.JsArray;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.maps.client.MapOptions;
import com.google.gwt.maps.client.MapType;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.maps.client.event.MapClickHandler;
import com.google.gwt.maps.client.event.MapMouseMoveHandler;
import com.google.gwt.maps.client.event.PolygonClickHandler;
import com.google.gwt.maps.client.geom.LatLng;
import com.google.gwt.maps.client.overlay.PolyStyleOptions;
import com.google.gwt.maps.client.overlay.Polygon;
import com.google.gwt.maps.client.overlay.PolygonOptions;
import com.smartgwt.client.types.Overflow;
import com.smartgwt.client.widgets.Window;
import com.smartgwt.client.widgets.layout.VLayout;
import com.spotimage.catalog.client.event.DeselectedSearchResultEvent;
import com.spotimage.catalog.client.event.DeselectedSearchResultFootprintEvent;
import com.spotimage.catalog.client.event.DeselectedSelectionEvent;
import com.spotimage.catalog.client.event.DeselectedSelectionFootprintEvent;
import com.spotimage.catalog.client.event.EventBus;
import com.spotimage.catalog.client.event.MoveToSelectionEvent;
import com.spotimage.catalog.client.event.RemoveFromSearchResultEvent;
import com.spotimage.catalog.client.event.RemoveFromSelectionEvent;
import com.spotimage.catalog.client.event.SearchResultEvent;
import com.spotimage.catalog.client.event.SelectedSearchResultEvent;
import com.spotimage.catalog.client.event.SelectedSearchResultFootprintEvent;
import com.spotimage.catalog.client.event.SelectedSelectionEvent;
import com.spotimage.catalog.client.event.SelectedSelectionFootprintEvent;
import com.spotimage.catalog.client.json.JSONScene;
import com.spotimage.catalog.client.json.JSONPoint;
import com.spotimage.catalog.client.json.JSONSearchResult;
import com.spotimage.catalog.client.shared.Scene;
import com.spotimage.catalog.client.ui.listgrid.SceneListGridRecord;

/**
 * Map panel
 */
public class MapPanel extends VLayout implements SearchResultEvent.Handler,
													SelectedSearchResultEvent.Handler,		
													DeselectedSearchResultEvent.Handler,
													MoveToSelectionEvent.Handler, 
													RemoveFromSelectionEvent.Handler,
													SelectedSelectionEvent.Handler,		
													DeselectedSelectionEvent.Handler, 
													RemoveFromSearchResultEvent.Handler {

	/** deselected result footprint color */
	private static final String FOOTPRINT_RESULT_DESELECTED = "#000088";
	/** selected result footprint color */
	private static final String FOOTPRINT_RESULT_SELECTED = "#00ffff";
	/** deselected selection footprint color */
	private static final String FOOTPRINT_SELECTION_DESELECTED = "#008800";
	/** selected selection footprint color */
	private static final String FOOTPRINT_SELECTION_SELECTED = "#aaffaa";
	/** region of interest */
	private Polygon roi;
	/** user is selecting region of interest ? */
	private boolean isSelecting = false;
	/** region of interest bounds */
	private List<LatLng> roiBounds;
	/** Google map */
	private MapWidget map;
	/** displayed search result scenes by polygon */
	private Map<Polygon, Scene> resultScenesByPolygon;
	/** displayed search result scenes by id */
	private Map<String, Polygon> resultScenesById;
	/** selected search result scene */
	private Polygon selectedResultScene;
	/** displayed selection scenes by polygon */
	private Map<Polygon, Scene> selectionScenesByPolygon;
	/** displayed selection scenes by id */
	private Map<String, Polygon> selectionScenesById;
	/** selected selection scene */
	private Polygon selectedSelectionScene;
	/** quicklook mouse over window */
	private Window mouseOverWindow;
	/** current window height (for internal resize purpose) */
	private int currentWindowHeight;

	/**
	 * Buid UI.
	 */
	public MapPanel() {
		EventBus.getInstance().addHandler(SearchResultEvent.TYPE, this);
		EventBus.getInstance().addHandler(SelectedSearchResultEvent.TYPE, this);
		EventBus.getInstance().addHandler(DeselectedSearchResultEvent.TYPE, this);
		EventBus.getInstance().addHandler(MoveToSelectionEvent.TYPE, this);
		EventBus.getInstance().addHandler(RemoveFromSelectionEvent.TYPE, this);
		EventBus.getInstance().addHandler(SelectedSelectionEvent.TYPE, this);
		EventBus.getInstance().addHandler(DeselectedSelectionEvent.TYPE, this);
		EventBus.getInstance().addHandler(RemoveFromSearchResultEvent.TYPE, this);
		
		setWidth("*");
		setHeight("100%");
		setOverflow(Overflow.AUTO);
		setBackgroundColor(Ui.BACKGROUND_COLOR);
		
		// polygon references
		resultScenesByPolygon = new HashMap<Polygon, Scene>();
		resultScenesById = new HashMap<String, Polygon>();
		selectionScenesByPolygon = new HashMap<Polygon, Scene>();
		selectionScenesById = new HashMap<String, Polygon>();
		
		setMap();
		
		// workaround for map resize ...
		currentWindowHeight = com.google.gwt.user.client.Window.getClientHeight();
		com.google.gwt.user.client.Window.addResizeHandler(new ResizeHandler() {
			//@Override
			public void onResize(ResizeEvent event) {
				resizeMap(event.getHeight());
			}
		});
	}

	/** 
	 * Buid map UI and add handlers for selecting region of interest.
	 */
	private void setMap() {
		MapOptions mapOptions = MapOptions.newInstance();
		mapOptions.setBackgroundColor(Ui.BACKGROUND_COLOR);
		
		map = new MapWidget(LatLng.newInstance(10.0, 0.0), 2, mapOptions);
		map.setGoogleBarEnabled(true);
		map.setUIToDefault();
		map.setContinuousZoom(true);
		map.setScrollWheelZoomEnabled(true);
		map.addMapType(MapType.getPhysicalMap());
		map.clearOverlays();

		map.addMapClickHandler(new MapClickHandler() {
			//@Override
			public void onClick(MapClickEvent event) {
				if (event.getOverlay() == null) {
					LatLng latLng = event.getLatLng();
					// reset list if new ROI
					if (!isSelecting)
						roiBounds = new ArrayList<LatLng>();
					// add point to list
					roiBounds.add(latLng);
					if (roiBounds.size() == 2)
						isSelecting = false;
					else
						isSelecting = true;
				}
			}
		});

		map.addMapMouseMoveHandler(new MapMouseMoveHandler() {
			//@Override
			public void onMouseMove(MapMouseMoveEvent event) {
				if (!isSelecting)	return;
				roiBounds.add(event.getLatLng());
				drawRoi();
				roiBounds.remove(roiBounds.size() - 1);
			}
		});

		addMember(map);
	}
	
	/**
	 * Draw region of interest.
	 */
	private void drawRoi() {
		// delete old roi
		if (roi != null) map.removeOverlay(roi);

		// generate polygon points
		LatLng[] polyPoints = new LatLng[5];
		polyPoints[0] = LatLng.newInstance(roiBounds.get(0).getLatitude(), roiBounds.get(0).getLongitude());
		polyPoints[1] = LatLng.newInstance(roiBounds.get(0).getLatitude(), roiBounds.get(1).getLongitude());
		polyPoints[2] = LatLng.newInstance(roiBounds.get(1).getLatitude(), roiBounds.get(1).getLongitude());
		polyPoints[3] = LatLng.newInstance(roiBounds.get(1).getLatitude(), roiBounds.get(0).getLongitude());
		polyPoints[4] = LatLng.newInstance(roiBounds.get(0).getLatitude(), roiBounds.get(0).getLongitude());

		// draw new polygon
		PolygonOptions polygonOptions = PolygonOptions.newInstance(false);
		roi = new Polygon(polyPoints, "#ff0000", 2, 1.0, "#ff0000", 0.3, polygonOptions);
		map.addOverlay(roi);
	}

	/**
	 * Is the region of interest set ?
	 * @return
	 */
	public boolean isRoiSet() {
		return (roi != null);
	}
	
	/**
	 * Get region of interest polygon.
	 * @return
	 */
	public Polygon getRoi() {
		return roi;
	}
	
	/**
	 * destroy the mouse over window.
	 */
	private void destroyMouseOverWindow() {
		if (mouseOverWindow != null) mouseOverWindow.destroy();
	}
	
	/**
	 * workaround for map resize ...
	 */
	public void resizeMap() {
		// TODO : to enhance ...
		map.setSize("100%", (getHeight() - 80) + "px");
	}
	
	/**
	 * workaround for map resize ...
	 * @param newWindowHeight
	 */
	public void resizeMap(int newWindowHeight) {
		// TODO : to enhance ...
		map.setHeight((getHeight() + (newWindowHeight - currentWindowHeight)) + "px");
		currentWindowHeight = newWindowHeight;
	}

	/**
	 * Receive {@link SearchResultEvent} and draw result scenes footprint on map.
	 */
	//@Override
	public void onSearchResult(SearchResultEvent e) {
		clearResultScenes();
		JSONSearchResult sr = e.getSearchResult();
		JsArray<JSONScene> resultScenes = sr.getScenes();
		for (int i = 0; i < resultScenes.length(); i++) {
			JSONScene scene = resultScenes.get(i);
			addFootprint(scene, false);
		}
	}

	/**
	 * Clear result scenes from map.
	 */
	private void clearResultScenes() {
		if (resultScenesByPolygon != null) {
			// remove old scene footprint
			Iterator<Polygon> iterator = resultScenesByPolygon.keySet().iterator();
			while (iterator.hasNext()) {
				 map.removeOverlay(iterator.next());
			}
		}
		resultScenesByPolygon.clear();
		resultScenesById.clear();
		selectedResultScene = null;
	}

	/**
	 * Add a footprint.
	 * @param scene
	 * @param isSelectionItem
	 */
	private void addFootprint(Scene scene, final boolean isSelectionItem) {
		String id = scene.getId();
		
		// set up object to use depending on isSelectionItem
		Map<String, Polygon> byIdScenes = (isSelectionItem)?selectionScenesById:resultScenesById;
		final Map<Polygon, Scene> byPolygonScenes = (isSelectionItem)?selectionScenesByPolygon:resultScenesByPolygon;
		final String selectedColor = (isSelectionItem)?FOOTPRINT_SELECTION_SELECTED:FOOTPRINT_RESULT_SELECTED;
		final String deselectedColor = (isSelectionItem)?FOOTPRINT_SELECTION_DESELECTED:FOOTPRINT_RESULT_DESELECTED;
		
		// check if footprint not already in
		if (selectionScenesById.get(id) != null) return;
		if (resultScenesById.get(id) != null) return;
		
		// scene footprint polygon
		LatLng[] polyPoints = new LatLng[5];
		JSONPoint upperLeft = scene.getUpperLeft();
		JSONPoint upperRight = scene.getUpperRight();
		JSONPoint lowerRight = scene.getLowerRight();
		JSONPoint lowerLeft = scene.getLowerLeft();
		polyPoints[0] = LatLng.newInstance(upperLeft.getLatitude(), upperLeft.getLongitude());
		polyPoints[1] = LatLng.newInstance(upperRight.getLatitude(), upperRight.getLongitude());
		polyPoints[2] = LatLng.newInstance(lowerRight.getLatitude(), lowerRight.getLongitude());
		polyPoints[3] = LatLng.newInstance(lowerLeft.getLatitude(), lowerLeft.getLongitude());
		polyPoints[4] = LatLng.newInstance(upperLeft.getLatitude(), upperLeft.getLongitude());
		Polygon	footprint = new Polygon(polyPoints, deselectedColor, 1, 1, deselectedColor, 0.1);
		map.addOverlay(footprint);
		byPolygonScenes.put(footprint, scene);
		byIdScenes.put(id, footprint);
		
		// scene polygon handlers
		footprint.addPolygonClickHandler(new PolygonClickHandler() {
			//@Override
			public void onClick(PolygonClickEvent event) {
				Polygon footprint = event.getSender();
				Scene scene = byPolygonScenes.get(footprint);
				Polygon selectedScene = (isSelectionItem)?selectedSelectionScene:selectedResultScene;
				GwtEvent<? extends EventHandler> selectedEvent = 
					(isSelectionItem)?new SelectedSelectionFootprintEvent(scene.getId()):new SelectedSearchResultFootprintEvent(scene.getId());
				GwtEvent<? extends EventHandler> deselectedEvent = 
					(isSelectionItem)?new DeselectedSelectionFootprintEvent(scene.getId()):new DeselectedSearchResultFootprintEvent(scene.getId());
				if (selectedScene == null) {
					selectedScene = footprint;
					footprint.setStrokeStyle(PolyStyleOptions.newInstance(selectedColor));
					EventBus.getInstance().fireEvent(selectedEvent);
				} else {
					if (selectedScene == footprint) {
						// deselect item
						selectedScene = null;
						footprint.setStrokeStyle(PolyStyleOptions.newInstance(deselectedColor));
						EventBus.getInstance().fireEvent(deselectedEvent);
					} else {
						// select item
						selectedScene = footprint;
						footprint.setStrokeStyle(PolyStyleOptions.newInstance(selectedColor));
						EventBus.getInstance().fireEvent(selectedEvent);
					}
				}
			}
		});
//		footprint.addPolygonMouseOverHandler(new PolygonMouseOverHandler() {
//			@Override
//			public void onMouseOver(PolygonMouseOverEvent event) {
//				Polygon footprint = event.getSender();
//				Scene scene = byPolygonScenes.get(footprint);
//				mouseOverWindow = new SceneQuicklookWindow(scene, true);
//			}
//		});
//		footprint.addPolygonMouseOutHandler(new PolygonMouseOutHandler() {
//			@Override
//			public void onMouseOut(PolygonMouseOutEvent event) {
//				destroyMouseOverWindow();
//			}
//		});
	}
	
	/**
	 * Receive {@link SelectedSearchResultEvent} and select search result scene footprint on map.
	 */
	//@Override
	public void onSelectedSearchResult(SelectedSearchResultEvent e) {
		Polygon footprint = resultScenesById.get(e.getId());
		if (footprint != null) {
			if (selectedResultScene != null) {
				// deselect previous selected
				footprint.setStrokeStyle(PolyStyleOptions.newInstance(FOOTPRINT_RESULT_DESELECTED));
			}
			// select new
			selectedResultScene = footprint;
			footprint.setStrokeStyle(PolyStyleOptions.newInstance(FOOTPRINT_RESULT_SELECTED));
		}
	}

	/**
	 * Receive {@link DeselectedSearchResultEvent} and deselect search result scene footprint on map.
	 */
	//@Override
	public void onDeselectedSearchResult(DeselectedSearchResultEvent e) {
		Polygon footprint = resultScenesById.get(e.getId());
		if (footprint != null) {
			selectedResultScene = null;
			footprint.setStrokeStyle(PolyStyleOptions.newInstance(FOOTPRINT_RESULT_DESELECTED));
		}
	}

	/**
	 * Receive {@link MoveToSelectionEvent} and move search result scene to selection scene.
	 */
	//@Override
	public void onMoveToSelection(MoveToSelectionEvent e) {
		SceneListGridRecord scene = e.getItem();
		String key = scene.getId();
		Polygon footprint = resultScenesById.get(key);
		if (footprint != null) {
			// remove from search result footprints
			resultScenesById.remove(key);
			resultScenesByPolygon.remove(footprint);
			if (selectedResultScene == footprint) selectedResultScene = null;
			map.removeOverlay(footprint);
		}
		// add to selection footprints
		addFootprint(scene, true);
	}

	/**
	 * Receive {@link RemoveFromSelectionEvent} and remove scene from selection.
	 */
	//@Override
	public void onRemoveFromSelection(RemoveFromSelectionEvent e) {
		SceneListGridRecord scene = e.getItem();
		String key = scene.getId();
		Polygon footprint = selectionScenesById.get(key);
		if (footprint != null) {
			// remove from selection footprints
			selectionScenesById.remove(key);
			selectionScenesByPolygon.remove(footprint);
			if (selectedSelectionScene == footprint) selectedSelectionScene = null;
			map.removeOverlay(footprint);
		}
	}
	
	/**
	 * Receive {@link RemoveFromSearchResultEvent} and remove scene from search result.
	 */
	//@Override
	public void onRemoveFromSearchResult(RemoveFromSearchResultEvent e) {
		SceneListGridRecord scene = e.getItem();
		String key = scene.getId();
		Polygon footprint = resultScenesById.get(key);
		if (footprint != null) {
			// remove from search result footprints
			resultScenesById.remove(key);
			resultScenesByPolygon.remove(footprint);
			if (selectedResultScene == footprint) selectedResultScene = null;
			map.removeOverlay(footprint);
		}
	}

	/**
	 * Receive {@link SelectedSelectionEvent} and select selection scene footprint on map.
	 */
	//@Override
	public void onSelectedSelection(SelectedSelectionEvent e) {
		Polygon footprint = selectionScenesById.get(e.getId());
		if (footprint != null) {
			if (selectedSelectionScene != null) {
				// deselect previous selected
				footprint.setStrokeStyle(PolyStyleOptions.newInstance(FOOTPRINT_SELECTION_DESELECTED));
			}
			// select new
			selectedSelectionScene = footprint;
			footprint.setStrokeStyle(PolyStyleOptions.newInstance(FOOTPRINT_SELECTION_SELECTED));
		}
	}

	/**
	 * Receive {@link DeselectedSelectionEvent} and deselect selection scene footprint on map.
	 */
	//@Override
	public void onDeselectedSelection(DeselectedSelectionEvent e) {
		Polygon footprint = selectionScenesById.get(e.getId());
		if (footprint != null) {
			selectedSelectionScene = null;
			footprint.setStrokeStyle(PolyStyleOptions.newInstance(FOOTPRINT_SELECTION_DESELECTED));
		}
	}
	
}
